package acme.routing.server;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import ws.tracking.*;

/**
 * Utility class for converting an xml representation of a set
 * of routes into a data structure, and vice versa,
 */
public class RouteParser
{
  /**
   * Structure that holds a set of routes and a list of 
   * new orders.
   */
  public static class ParseResult
  {
    public List<Vehicle> routes;
    public List<Order> newOrders;    
  }
  
  /**
   * Converts a set of routes into an XML representation.
   * 
   * @param routes a set of routes
   * @param out the output stream on which to write the result
   * @throws ParserConfigurationException
   * @throws TransformerException
   */
  public static void createXml(List<Vehicle> routes, OutputStream out) 
  throws ParserConfigurationException, TransformerException
  {
    createXml(routes, null, out); 
  }

  /**
   * Converts a set of routes and a list of orders into an XML
   * representation.
   * 
   * @param routes a set of routes
   * @param newOrders a list of new orders
   * @param out the output stream on which to write the result
   * @throws ParserConfigurationException
   * @throws TransformerException
   */
  public static void createXml(List<Vehicle> routes, List<Order> newOrders, OutputStream out) 
    throws ParserConfigurationException, TransformerException
  {
    Document dom = createDomTree(routes, newOrders);
    Result result = new StreamResult(out);
    Transformer t = TransformerFactory.newInstance().newTransformer();
    t.setOutputProperty(OutputKeys.METHOD, "xml");
    t.setOutputProperty(OutputKeys.INDENT, "yes");
    t.transform(new DOMSource(dom), result);
  }

  /**
   * Converts an XML representation of a set of routes and new orders
   * into a pair of data structures.
   * 
   * @param in the input stream from which to read the XML file
   * @return a pair of values consisting of a set of routes and
   *         a list of new orders
   * @throws ParserConfigurationException
   * @throws SAXException
   * @throws IOException
   */
  public static ParseResult parse(InputStream in)  
    throws ParserConfigurationException, SAXException, IOException 
    {

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    ArrayList<Vehicle> routes = new ArrayList<Vehicle>();
    ArrayList<Order> newOrders;

    Document dom;
    DocumentBuilder db = dbf.newDocumentBuilder();
    dom = db.parse(in);

    Element root = dom.getDocumentElement();
    NodeList nl = root.getElementsByTagName("routes");
    if (nl != null && nl.getLength() > 0) 
    {
      // should be only one element   
      Element element = (Element) nl.item(0);
      nl = element.getElementsByTagName("vehicle");
      if (nl != null)
      {
        for (int i = 0; i < nl.getLength(); i++) 
        {
          Element v = (Element) nl.item(i);
          Vehicle vehicle = constructVehicle(v);
          routes.add(vehicle);
        }
      }
    }

    nl = root.getElementsByTagName("neworders");
    if (nl != null && nl.getLength() > 0) 
    {
      // should be only one element   
      Element element = (Element) nl.item(0);
      newOrders = createOrderList(element);
    }
    else
    {
      newOrders = new ArrayList<Order>();
    }

    ParseResult ret = new ParseResult();
    ret.routes = routes;
    ret.newOrders = newOrders;
    return ret;
  }
  
  //
  // Helper methods for creating data structure
  //
  
  private static Vehicle constructVehicle(Element element)
  {
    String idString = element.getAttribute("id");
    int id = Integer.parseInt(idString);
    String capacityString = element.getAttribute("capacity");
    double capacity = Double.parseDouble(capacityString);
    
    NodeList nl = element.getElementsByTagName("stop");
    ArrayList<Stop> stops = new ArrayList<Stop>();
    if (nl != null)
    {
      for (int i = 0; i < nl.getLength(); ++i)
      {
        Element s = (Element) nl.item(i);
        Stop stop = constructStop(s);
        stops.add(stop);
      }
    }

    Vehicle ret = new Vehicle();
    ret.setId(id);
    ret.setCapacity(capacity);
    
    for (Stop s : stops)
    {
      ret.getCurrentRoute().add(s);
    }
    return ret;
  }
  
  private static Stop constructStop(Element element)
  {
    String location = element.getAttribute("location");
    ArrayList<Order> pickups;
    ArrayList<Order> deliveries;
    
    NodeList nl = element.getElementsByTagName("pickup");
    if (nl != null && nl.getLength() > 0)
    {
      Element pickup = (Element) nl.item(0);
      pickups = createOrderList(pickup);
    }
    else
    {
      pickups = new ArrayList<Order>();
    }
    nl = element.getElementsByTagName("delivery");
    if (nl != null && nl.getLength() > 0)
    {
      Element delivery = (Element) nl.item(0);
      deliveries = createOrderList(delivery);
    }
    else
    {
      deliveries = new ArrayList<Order>();
    }

    Stop ret = new Stop();
    ret.setLocation(location);
    for (Order o : pickups)
    {
      ret.getPickups().add(o);
    }
    for (Order o : deliveries)
    {
      ret.getDeliveries().add(o);
    }
    return ret;
  }
  
  private static ArrayList<Order> createOrderList(Element element)
  {
    ArrayList<Order> orders = new ArrayList<Order>();
    NodeList orderList = element.getElementsByTagName("order");
    if (orderList != null)
    {
      for (int i = 0; i < orderList.getLength(); ++i)
      {
        Element orderElement = (Element) orderList.item(i);
        Order order = createOrder(orderElement);
        orders.add(order);
      }
    }

    return orders;
  }
  
  private static Order createOrder(Element element) throws NumberFormatException
  {
    String orderidString = element.getAttribute("orderid");
    int orderid = Integer.parseInt(orderidString);
    String customerid = element.getAttribute("customerid"); // not used
    String src = element.getAttribute("src");
    String dst = element.getAttribute("dst");
    String weightString = element.getAttribute("weight");
    double weight = Double.parseDouble(weightString);
    String deadlineString = element.getAttribute("deadline");
    SimpleDate deadline = createSimpleDate(deadlineString);
    Order ret = new Order();
    ret.setDeadline(deadline);
    ret.setDst(dst);
    ret.setId(orderid);
    ret.setSrc(src);
    ret.setWeight(weight);
    return ret;
  }
  
  // create SimpleDate from form "2008/12/05"
  private static SimpleDate createSimpleDate(String s) throws NumberFormatException
  {
    if (s != null)
    {
      String[] ss = s.split("/");
      if (ss.length == 3)
      {
        SimpleDate ret = new SimpleDate();
        ret.setDay(Integer.parseInt(ss[2]));
        ret.setMonth(Integer.parseInt(ss[1]));
        ret.setYear(Integer.parseInt(ss[0]));
        return ret;
      }
    }
    return new SimpleDate();
  }

  
  //
  // Helper methods for creating xml file
  //
  
  private static Document createDomTree(List<Vehicle> routes, List<Order> newOrders) 
      throws ParserConfigurationException
  {
    DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 
    Document dom = db.newDocument();
    Element root = dom.createElement("routedata");
    dom.appendChild(root);
    Element routeElement = dom.createElement("routes");
    root.appendChild(routeElement);   
    if (routes != null)
    {
      for (Vehicle v : routes)
      {
        routeElement.appendChild(createVehicleElement(dom, v));
      }
    }
    
    Element newOrderElement = dom.createElement("neworders");
    root.appendChild(newOrderElement);
    if (newOrders != null)
    {
      for (Order order : newOrders)
      {
        newOrderElement.appendChild(createOrderElement(dom, order));
      }
    }
    return dom;
  }
  
  private static Element createVehicleElement(Document dom, Vehicle vehicle)
  {
    Element vehicleElement = dom.createElement("vehicle");
    vehicleElement.setAttribute("id", String.valueOf(vehicle.getId()));
    vehicleElement.setAttribute("capacity", String.valueOf(vehicle.getCapacity()));
    for (Stop s : vehicle.getCurrentRoute())
    {
      vehicleElement.appendChild(createStopElement(dom, s));
    }
    return vehicleElement;
  }
  
  private static Element createStopElement(Document dom, Stop stop)
  {
    Element stopElement = dom.createElement("stop");
    stopElement.setAttribute("location", stop.getLocation());
    Element pickupElement = createOrderListElement(dom, stop.getPickups(), "pickup");
    stopElement.appendChild(pickupElement);
    Element deliveryElement = createOrderListElement(dom, stop.getDeliveries(), "delivery");
    stopElement.appendChild(deliveryElement);
    return stopElement;
  }
  
  private static Element createOrderListElement(Document dom, List<Order> orders, String name)
  {
    Element listElement = dom.createElement(name);
    for (Order order : orders)
    {
      Element orderElement = createOrderElement(dom, order);
      listElement.appendChild(orderElement);
    }
    return listElement;
  }
  
  private static Element createOrderElement(Document dom, Order order)
  {
    Element orderElement = dom.createElement("order");
    orderElement.setAttribute("orderid", String.valueOf(order.getId()));
    orderElement.setAttribute("customerid", String.valueOf(0)); // unknown
    orderElement.setAttribute("src", order.getSrc());
    orderElement.setAttribute("dst", order.getDst());
    orderElement.setAttribute("weight", String.valueOf(order.getWeight()));
    orderElement.setAttribute("deadline", createSimpleDateString(order.getDeadline()));
    return orderElement;
  }
  
  private static String createSimpleDateString(SimpleDate date)
  {
    return date.getYear() + "/" + 
      String.format("%02d", date.getMonth()) + "/" +
      String.format("%02d", date.getDay());
  }
  
  private static void testOutput(Document dom) throws Exception
  {
    Result result = new StreamResult(System.out);
    Transformer t = TransformerFactory.newInstance().newTransformer();
    t.setOutputProperty(OutputKeys.METHOD, "xml");
    t.transform(new DOMSource(dom), result);
  }
  
}
